<?php

declare(strict_types=1);
namespace App\Utils;

use Exception;

class Im
{
    public const array MsgTypes = [
        'IM_BUG' => 0,
        'IM_HEARTBEAT_CMD' => 30,
        'IM_LOGIN_CMD' => 31,
        'IM_LOGIN_RESULT' => 32,
        'IM_QUIT_CMD' => 33,
        'IM_QUIT_RESULT' => 34,
        'IM_DO_CMD' => 35,
        'IM_DO_RESULT' => 36,
        'IM_ACTION_RESULT' => 37,
        'IM_SEND_MSG_CMD' => 38,
        'IM_SEND_MSG_RESULT' => 39,
        'IM_NEW_MSG_RESULT' => 40,
        'IM_NEW_APPLY_RESULT' => 41,
        'IM_CHAT_JOIN_CMD' => 42,
        'IM_CHAT_JOIN_RESULT' => 43,
        'IM_SEND_CHAT_MSG_CMD' => 44,
        'IM_SEND_CHAT_MSG_RESULT' => 45,
        'IM_NEW_CHAT_MSG_RESULT' => 46,
        'IM_SESSION_RESULT' => 47,
        'IM_SESSION_LIST_RESULT' => 48,
        'IM_CHAT_QUIT_CMD' => 49,
        'IM_CHAT_QUIT_RESULT' => 50,
    ];

    /**
     * @param mixed $receive
     * @throws Exception
     */
    public static function Parse($receive)
    {
        if (! $receive) {
            return false;
        }
        if ($receive->opcode == SWOOLE_WEBSOCKET_OPCODE_BINARY) {
            $code = unpack('N', $receive->data)[1];
            if (! in_array($code, self::MsgTypes)) {
                throw new Exception('消息类型未识别');
            }
            $msgTypeName = self::convertMsgTypeName(array_search($code, self::MsgTypes));
            $protoClassName = '\\App\\Proto\\Im\\' . $msgTypeName;
            $msg = new $protoClassName();
            $msg->mergeFromString(substr($receive->data, 4));
            return [
                'msgType' => $msgTypeName,
                'msgBody' => new_json_decode($msg->serializeToJsonString()),
            ];
        }
        if ($receive->opcode == SWOOLE_WEBSOCKET_OPCODE_TEXT) {
            return $receive->data;
        }
        return false;
    }

    public static function Create(string $msgType, array $msgBody = []): false|string
    {
        $code = self::getMsgTypeCode($msgType);
        if ($code === false) {
            return false;
        }
        $msg = self::Pack($msgType, $msgBody);
        if ($msg === false) {
            return false;
        }
        return pack('N', $code) . $msg->serializeToString();
    }

    /**
     * proto
     * @param string $msgType
     * @param $msgBody
     * @return mixed
     */
    public static function Pack(string $msgType, $msgBody) {
        $class = '\\App\\Proto\\Im\\' . $msgType;
        if(!class_exists($class)) {
            return false;
        }
        $msg = new $class();
        foreach ($msgBody as $filed => $value) {
            if (str_contains($filed, '_')) {
                $filed = self::convertMsgTypeName($filed);
            }
            $method = 'set' . ucfirst($filed);
            if (method_exists($msg, $method)) {
                $msg->{$method}($value);
            }
        }
        return $msg;
    }

    private static function getMsgTypeCode($msgTypeName): false|int|string
    {
        $msgType = strtoupper(preg_replace('/(?<!^)[A-Z]/', '_$0', $msgTypeName));
        return self::MsgTypes[$msgType];
    }

    private static function convertMsgTypeName($msgType): string
    {
        $arr = explode('_', strtolower($msgType));
        $newMsgTypeName = '';
        foreach ($arr as $name) {
            $newMsgTypeName .= ucfirst($name);
        }
        return $newMsgTypeName;
    }
}
